<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    body {
      width: 100%;
      height: 100vh;
      overflow: hidden;
      /* background-color: black; */
      margin: 0;
    }

    canvas {
      width: 100%;
      height: 100%;
      background: rgb(0, 0, 0);
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
    }

    #input {
      font-size: 30px;
      color: white;
      outline: none;
      display: block;
      width: 300px;
      position: absolute;
      left: calc(50% - 150px);
      top: 30%;
      border: none;
      background: transparent;
      border-bottom: 2px solid rgba(255, 255, 255, 0.822);
      text-align: center;
    }
  </style>
</head>

<body>
  <input id="input" type="text" maxlength="8" oninput="changeText(input.value)">
</body>
<script>
  /*jshint esversion:6*/
  var canvas = document.createElement("canvas");
  var w = window.innerWidth;
  var h = window.innerHeight;
  document.body.appendChild(canvas);
  canvas.width = w;
  canvas.height = h;
  var ctx = canvas.getContext('2d');

  //储存粒子数组
  var particles = [];
  var max_radius = 3; //粒子的最大半径
  var min_radius = 0.95;  //粒子的最小半径
  var drag = 20; //切换文字，粒子组成速度 值越小越快
  // var colors = ['#' + ('00000' + (Math.random() * 0x1000000 << 0).toString(16)).slice(-6)];//粒子颜色
  var bool = false; //默认粒子文字是直接显示，反之则是粒子打乱后显示
  var textFont = 80; //粒子文字大小
  //初始鼠标坐标
  var mouse = {
    x: -1000,
    y: -1000
  };
  //pc 鼠标移动
  canvas.onmousemove = function(e) {
    mouse.x = e.clientX;
    mouse.y = e.clientY;
  };
  //mobile 手势移动
  canvas.ontouchmove = function(e) {
    mouse.x = e.touches[0].clientX;
    mouse.y = e.touches[0].clientY;
  };

  // 返回一个数的平方根
  function distance(x, y, x1, y1) {
    // sqrt() 方法可返回一个数的平方根。
    // a² + b² = c²
    return Math.sqrt((x1 - x) * (x1 - x) + (y1 - y) * (y1 - y));
  }

  //创建粒子文字或改变粒子文字
  //根据文字生成相对应的 粒子 ，并储存到 particles 数组内
  function changeText(text) {
    ctx.clearRect(0, 0, w, h);
    var current = 0; //初始化 粒子个数 默认为0个
    var temp; //初始化 粒子坐标
    var radius; //初始化 粒子半径
    var color; //初始化 粒子颜色
    //文字部分
    ctx.fillStyle = "rgb(255, 255, 255)";
    ctx.font = textFont + "px -apple-system";
    // 实现文字居中
    ctx.fillText(text, w / 2 - ctx.measureText(text).width / 2, h / 2 + textFont / 2);
    //通过 getImageData() 复制画布上指定矩形的像素数据
    var data = ctx.getImageData(0, 0, w, h).data;
    // console.log(data)
    // +=4 对于 ImageData 对象中的每个像素，都存在着4方面的信息，即 RGBA 值
    //此处 +=8 是为了避免资源浪费，减少粒子个数
    // += 越多，避免筛选的粒子越少，符合条件的，显示的粒子越少
    //data 会有特别 特别多
    for (let i = 0; i < data.length; i += 4) {
      temp = {
        x: (i / 4) % w,
        y: ~~((i / 4) / w)
      };

      //比较结果上的区别
      // != 返回同类型值比较结果。
      // !== 不同类型不比较，且无结果，同类型才比较。
      //比较过程上的区别
      //!= 比较时，若类型不同，会偿试转换类型。
      //!== 只有相同类型才会比较。
      //此处使用 !==,只能说是为了节约资源

      //http://www.fly63.com/article/detial/2802
      //~~ 同Math.floor() 向下取整
      //判断不为0 && 随机一个数值取值为 1 时，才生成一个粒子
      if (data[i] !== 0 && ~~(Math.random() * 5) == 1) {
        //因为之前定义文字颜色为 rgb(255, 255, 255)w
        //所以此处判断当遇到像素色值为 255 时，不显示粒子（若加上此处判断，则会实现描边效果，反之为填充效果）
        // if (data[i + 4] !== 255 || data[i - 4] !== 255 || data[i + w * 4] !== 255 || data[i - w * 4] !== 255) {
        //判断当再次调用 changeText 改变文字时，重新打乱并组成新的粒子文字
        if (current < particles.length) {
          particles[current].target = temp
          // console.log(current)
        } else {
          //随机生成一个 min max 之间的粒子半径
          radius = max_radius - Math.random() * min_radius;
          //粒子从随机位置生成
          if (!bool) {
            temp = {
              x: Math.random() * w,
              y: Math.random() * h
            };
          }

          //取粒子随机色
          // color = colors[~~(Math.random() * colors.length)];

          color = '#' + ('00000' + (Math.random() * 0x1000000 << 0).toString(16)).slice(-6);

          //创建一个粒子，并添加到粒子数组中
          var p = new Particle(
            temp,
            { x: (i / 4) % w, y: ~~((i / 4) / w) },
            { x: 0, y: 0 },
            color,
            radius);

          particles.push(p);
        }
        // ++current先自己加1，再做别的事情
        //存储符合条件的粒子数量
        ++current;
      }
      // }
    }
    bool = false;
    //当粒子文字变化时，删除多余的粒子
    particles.splice(current, particles.length - current);
  }

  //创建一个粒子的类，制定相关属性
  class Particle {
    constructor(pos, target, vel, color, radius) {
      this.pos = pos; //粒子坐标(随机位置或粒子坐标，方便实现文字切换时粒子打乱效果)
      this.target = target; //粒子坐标
      this.vel = vel; //记录鼠标移动打散的粒子坐标
      this.color = color; //粒子颜色
      this.radius = radius; //粒子半径
      var arr = [-1, 1];
      //粒子大小的变化速率
      this.direction = arr[~~(Math.random() * 2)] * Math.random() / 10;
    }
    //实时更新数据
    update() {
      //粒子半径的变化
      this.radius += this.direction;
      this.vel.x = (this.pos.x - this.target.x) / drag;
      this.vel.y = (this.pos.y - this.target.y) / drag;

      //鼠标移动到文字上时的效果
      //根据勾股定理 a² + b² = c²，鼠标坐标位置 - 粒子坐标位置 得到 a b 的长度，以此求得c的长度(及打散半径)
      //50 鼠标打散范围，当 c 的长度小于 50 时执行
      if (distance(this.pos.x, this.pos.y, mouse.x, mouse.y) < 50) {
        this.vel.x += this.vel.x - (this.pos.x - mouse.x) / 15;
        this.vel.y += this.vel.y - (this.pos.y - mouse.y) / 15;
      }

      //实现粒子变大缩小一直循环的效果
      if (this.radius >= max_radius) {
        this.direction *= -1;
      }
      if (this.radius <= 1) {
        this.direction *= -1;
      }

      this.pos.x -= this.vel.x;
      this.pos.y -= this.vel.y;
      this.draw()
    }
    //绘制粒子
    draw() {
      ctx.beginPath();
      ctx.fillStyle = this.color;
      ctx.arc(this.pos.x, this.pos.y, this.radius, 0, Math.PI * 2);
      ctx.fill();
    }
  }

  //绘制粒子文字
  function draw() {
    ctx.clearRect(0, 0, w, h);
    for (let n of particles) {
      n.update()
    }
  }

  //每帧执行的粒子变化动画
  function animation() {
    // 相当于每一帧都会执行一次方法
    window.requestAnimationFrame(animation)
    draw()
  }
  animation()

  //默认显示粒子文字为 input
  changeText("陈玉龙是狗");
</script>

</html>